//===--- Outlining.cpp - Outlining value operations -----------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
//  This file implements IR generation for outlined value operations in Swift.
//
//===----------------------------------------------------------------------===//

#include "polarphp/ast/GenericEnvironment.h"
#include "polarphp/irgen/internal/Outlining.h"
#include "polarphp/irgen/internal/Explosion.h"
#include "polarphp/irgen/internal/GenInterface.h"
#include "polarphp/irgen/internal/IRGenFunction.h"
#include "polarphp/irgen/internal/IRGenMangler.h"
#include "polarphp/irgen/internal/IRGenModule.h"
#include "polarphp/irgen/internal/LoadableTypeInfo.h"
#include "polarphp/irgen/internal/LocalTypeDataKind.h"
#include "polarphp/irgen/internal/MetadataRequest.h"

using namespace polar;
using namespace irgen;

void OutliningMetadataCollector::collectTypeMetadataForLayout(PILType type) {
   // If the type has no archetypes, we can emit it from scratch in the callee.
   if (!type.hasArchetype()) {
      return;
   }

   // Substitute opaque types if allowed.
   auto origType = type;
   type =
      IGF.IGM.substOpaqueTypesWithUnderlyingTypes(type, CanGenericSignature());

   auto formalType = type.getAstType();
   auto &ti = IGF.IGM.getTypeInfoForLowered(formalType);

   // We don't need the metadata for fixed size types or types that are not ABI
   // accessible. Outlining will call the value witness of the enclosing type of
   // non ABI accessible field/element types.
   if ((!origType.getAstType()->hasOpaqueArchetype() &&
        isa<FixedTypeInfo>(ti)) ||
       !ti.isABIAccessible()) {
      return;
   }

   // If the type is a legal formal type, add it as a formal type.
   // FIXME: does this force us to emit a more expensive metadata than we need
   // to?
   if (formalType->isLegalFormalType()) {
      return collectFormalTypeMetadata(formalType);
   }

   auto key = LocalTypeDataKey(type.getAstType(),
                               LocalTypeDataKind::forRepresentationTypeMetadata());
   if (Values.count(key)) return;

   auto metadata = IGF.emitTypeMetadataRefForLayout(type);
   Values.insert({key, metadata});
}

void OutliningMetadataCollector::collectFormalTypeMetadata(CanType type) {
   // If the type has no archetypes, we can emit it from scratch in the callee.
   assert(type->hasArchetype());

   auto key = LocalTypeDataKey(type, LocalTypeDataKind::forFormalTypeMetadata());
   if (Values.count(key)) return;

   auto metadata = IGF.emitTypeMetadataRef(type);
   Values.insert({key, metadata});
}


void OutliningMetadataCollector::addMetadataArguments(
   SmallVectorImpl<llvm::Value*> &args) const {
   for (auto &pair : Values) {
      auto metadata = pair.second;
      assert(metadata->getType() == IGF.IGM.TypeMetadataPtrTy);
      args.push_back(metadata);
   }
}

void OutliningMetadataCollector::addMetadataParameterTypes(
   SmallVectorImpl<llvm::Type*> &paramTys) const {
   for (auto &pair : Values) {
      auto *metadata = pair.second;
      paramTys.push_back(metadata->getType());
   }
}

void OutliningMetadataCollector::bindMetadataParameters(IRGenFunction &IGF,
                                                        Explosion &params) const {
   // Note that our parameter IGF intentionally shadows the IGF that this
   // collector was built with.
   for (auto &pair : Values) {
      llvm::Value *arg = params.claimNext();

      auto key = pair.first;
      assert(key.Kind.isAnyTypeMetadata());
      setTypeMetadataName(IGF.IGM, arg, key.Type);
      IGF.setUnscopedLocalTypeData(key, MetadataResponse::forComplete(arg));
   }
}

std::pair<CanType, CanGenericSignature>
irgen::getTypeAndGenericSignatureForManglingOutlineFunction(PILType type) {
   auto loweredType = type.getAstType();
   if (loweredType->hasArchetype()) {
      GenericEnvironment *env = nullptr;
      loweredType.findIf([&env](Type t) -> bool {
         if (auto arch = t->getAs<ArchetypeType>()) {
            auto root = arch->getRoot();
            if (!isa<PrimaryArchetypeType>(root))
               return false;
            env = root->getGenericEnvironment();
            return true;
         }
         return false;
      });
      assert(env && "has archetype but no archetype?!");
      return {loweredType->mapTypeOutOfContext()->getCanonicalType(),
              env->getGenericSignature()->getCanonicalSignature()};
   }
   return {loweredType, nullptr};
}

void TypeInfo::callOutlinedCopy(IRGenFunction &IGF,
                                Address dest, Address src, PILType T,
                                IsInitialization_t isInit,
                                IsTake_t isTake) const {
   OutliningMetadataCollector collector(IGF);
   if (T.hasArchetype()) {
      collectMetadataForOutlining(collector, T);
   }
   collector.emitCallToOutlinedCopy(dest, src, T, *this, isInit, isTake);
}

void OutliningMetadataCollector::emitCallToOutlinedCopy(
   Address dest, Address src,
   PILType T, const TypeInfo &ti,
   IsInitialization_t isInit, IsTake_t isTake) const {
   llvm::SmallVector<llvm::Value *, 4> args;
   args.push_back(IGF.Builder.CreateElementBitCast(src, ti.getStorageType())
                     .getAddress());
   args.push_back(IGF.Builder.CreateElementBitCast(dest, ti.getStorageType())
                     .getAddress());
   addMetadataArguments(args);

   llvm::Constant *outlinedFn;
   if (isInit && isTake) {
      outlinedFn =
         IGF.IGM.getOrCreateOutlinedInitializeWithTakeFunction(T, ti, *this);
   } else if (isInit) {
      outlinedFn =
         IGF.IGM.getOrCreateOutlinedInitializeWithCopyFunction(T, ti, *this);
   } else if (isTake) {
      outlinedFn =
         IGF.IGM.getOrCreateOutlinedAssignWithTakeFunction(T, ti, *this);
   } else {
      outlinedFn =
         IGF.IGM.getOrCreateOutlinedAssignWithCopyFunction(T, ti, *this);
   }

   llvm::CallInst *call = IGF.Builder.CreateCall(outlinedFn, args);
   call->setCallingConv(IGF.IGM.DefaultCC);
}

llvm::Constant *IRGenModule::getOrCreateOutlinedInitializeWithTakeFunction(
   PILType T, const TypeInfo &ti,
   const OutliningMetadataCollector &collector) {
   auto manglingBits = getTypeAndGenericSignatureForManglingOutlineFunction(T);
   auto funcName =
      IRGenMangler().mangleOutlinedInitializeWithTakeFunction(manglingBits.first,
                                                              manglingBits.second);

   return getOrCreateOutlinedCopyAddrHelperFunction(T, ti, collector, funcName,
                                                    [](IRGenFunction &IGF, Address dest, Address src,
                                                       PILType T, const TypeInfo &ti) {
                                                       ti.initializeWithTake(IGF, dest, src, T, true);
                                                    });
}

llvm::Constant *IRGenModule::getOrCreateOutlinedInitializeWithCopyFunction(
   PILType T, const TypeInfo &ti,
   const OutliningMetadataCollector &collector) {
   auto manglingBits = getTypeAndGenericSignatureForManglingOutlineFunction(T);
   auto funcName =
      IRGenMangler().mangleOutlinedInitializeWithCopyFunction(manglingBits.first,
                                                              manglingBits.second);

   return getOrCreateOutlinedCopyAddrHelperFunction(T, ti, collector, funcName,
                                                    [](IRGenFunction &IGF, Address dest, Address src,
                                                       PILType T, const TypeInfo &ti) {
                                                       ti.initializeWithCopy(IGF, dest, src, T, true);
                                                    });
}

llvm::Constant *IRGenModule::getOrCreateOutlinedAssignWithTakeFunction(
   PILType T, const TypeInfo &ti,
   const OutliningMetadataCollector &collector) {
   auto manglingBits = getTypeAndGenericSignatureForManglingOutlineFunction(T);
   auto funcName =
      IRGenMangler().mangleOutlinedAssignWithTakeFunction(manglingBits.first,
                                                          manglingBits.second);

   return getOrCreateOutlinedCopyAddrHelperFunction(T, ti, collector, funcName,
                                                    [](IRGenFunction &IGF, Address dest, Address src,
                                                       PILType T, const TypeInfo &ti) {
                                                       ti.assignWithTake(IGF, dest, src, T, true);
                                                    });
}

llvm::Constant *IRGenModule::getOrCreateOutlinedAssignWithCopyFunction(
   PILType T, const TypeInfo &ti,
   const OutliningMetadataCollector &collector) {
   auto manglingBits = getTypeAndGenericSignatureForManglingOutlineFunction(T);
   auto funcName =
      IRGenMangler().mangleOutlinedAssignWithCopyFunction(manglingBits.first,
                                                          manglingBits.second);

   return getOrCreateOutlinedCopyAddrHelperFunction(T, ti, collector, funcName,
                                                    [](IRGenFunction &IGF, Address dest, Address src,
                                                       PILType T, const TypeInfo &ti) {
                                                       ti.assignWithCopy(IGF, dest, src, T, true);
                                                    });
}

llvm::Constant *IRGenModule::getOrCreateOutlinedCopyAddrHelperFunction(
   PILType T, const TypeInfo &ti,
   const OutliningMetadataCollector &collector,
   StringRef funcName,
   CopyAddrHelperGenerator generator) {
   auto ptrTy = ti.getStorageType()->getPointerTo();

   llvm::SmallVector<llvm::Type *, 4> paramTys;
   paramTys.push_back(ptrTy);
   paramTys.push_back(ptrTy);
   collector.addMetadataParameterTypes(paramTys);

   return getOrCreateHelperFunction(funcName, ptrTy, paramTys,
                                    [&](IRGenFunction &IGF) {
                                       auto params = IGF.collectParameters();
                                       Address src = ti.getAddressForPointer(params.claimNext());
                                       Address dest = ti.getAddressForPointer(params.claimNext());
                                       collector.bindMetadataParameters(IGF, params);
                                       generator(IGF, dest, src, T, ti);
                                       IGF.Builder.CreateRet(dest.getAddress());
                                    },
                                    true /*setIsNoInline*/);
}

void TypeInfo::callOutlinedDestroy(IRGenFunction &IGF,
                                   Address addr, PILType T) const {
   OutliningMetadataCollector collector(IGF);
   if (T.hasArchetype()) {
      collectMetadataForOutlining(collector, T);
   }
   collector.emitCallToOutlinedDestroy(addr, T, *this);
}

void OutliningMetadataCollector::emitCallToOutlinedDestroy(
   Address addr, PILType T, const TypeInfo &ti) const {
   llvm::SmallVector<llvm::Value *, 4> args;
   args.push_back(IGF.Builder.CreateElementBitCast(addr, ti.getStorageType())
                     .getAddress());
   addMetadataArguments(args);

   auto outlinedFn =
      IGF.IGM.getOrCreateOutlinedDestroyFunction(T, ti, *this);

   llvm::CallInst *call = IGF.Builder.CreateCall(outlinedFn, args);
   call->setCallingConv(IGF.IGM.DefaultCC);
}

llvm::Constant *IRGenModule::getOrCreateOutlinedDestroyFunction(
   PILType T, const TypeInfo &ti,
   const OutliningMetadataCollector &collector) {
   IRGenMangler mangler;
   auto manglingBits = getTypeAndGenericSignatureForManglingOutlineFunction(T);
   auto funcName = mangler.mangleOutlinedDestroyFunction(manglingBits.first,
                                                         manglingBits.second);

   auto ptrTy = ti.getStorageType()->getPointerTo();
   llvm::SmallVector<llvm::Type *, 4> paramTys;
   paramTys.push_back(ptrTy);
   collector.addMetadataParameterTypes(paramTys);

   return getOrCreateHelperFunction(funcName, ptrTy, paramTys,
                                    [&](IRGenFunction &IGF) {
                                       Explosion params = IGF.collectParameters();
                                       Address addr = ti.getAddressForPointer(params.claimNext());
                                       collector.bindMetadataParameters(IGF, params);

                                       ti.destroy(IGF, addr, T, true);

                                       IGF.Builder.CreateRet(addr.getAddress());
                                    },
                                    true /*setIsNoInline*/);
}

llvm::Constant *IRGenModule::getOrCreateRetainFunction(const TypeInfo &ti,
                                                       PILType t,
                                                       llvm::Type *llvmType) {
   auto *loadableTI = cast<LoadableTypeInfo>(&ti);
   IRGenMangler mangler;
   auto manglingBits =
      getTypeAndGenericSignatureForManglingOutlineFunction(t);
   auto funcName = mangler.mangleOutlinedRetainFunction(manglingBits.first,
                                                        manglingBits.second);
   llvm::Type *argTys[] = {llvmType};
   return getOrCreateHelperFunction(
      funcName, llvmType, argTys,
      [&](IRGenFunction &IGF) {
         auto it = IGF.CurFn->arg_begin();
         Address addr(&*it++, loadableTI->getFixedAlignment());
         Explosion loaded;
         loadableTI->loadAsTake(IGF, addr, loaded);
         Explosion out;
         loadableTI->copy(IGF, loaded, out, irgen::Atomicity::Atomic);
         (void)out.claimAll();
         IGF.Builder.CreateRet(addr.getAddress());
      },
      true /*setIsNoInline*/);
}

llvm::Constant *
IRGenModule::getOrCreateReleaseFunction(const TypeInfo &ti,
                                        PILType t,
                                        llvm::Type *llvmType) {
   auto *loadableTI = cast<LoadableTypeInfo>(&ti);
   IRGenMangler mangler;
   auto manglingBits =
      getTypeAndGenericSignatureForManglingOutlineFunction(t);
   auto funcName = mangler.mangleOutlinedReleaseFunction(manglingBits.first,
                                                         manglingBits.second);
   llvm::Type *argTys[] = {llvmType};
   return getOrCreateHelperFunction(
      funcName, llvmType, argTys,
      [&](IRGenFunction &IGF) {
         auto it = IGF.CurFn->arg_begin();
         Address addr(&*it++, loadableTI->getFixedAlignment());
         Explosion loaded;
         loadableTI->loadAsTake(IGF, addr, loaded);
         loadableTI->consume(IGF, loaded, irgen::Atomicity::Atomic);
         IGF.Builder.CreateRet(addr.getAddress());
      },
      true /*setIsNoInline*/);
}
